package handler

import (
	"context"
	"encoding/json"
	"github.com/apache/rocketmq-client-go/v2/primitive"
	"github.com/opentracing/opentracing-go"
	"go.uber.org/zap"
	"google.golang.org/grpc/codes"
	"sale_system/order_srv/global"
	"sale_system/order_srv/model"
	"sale_system/order_srv/proto"
)

type OrderListener struct {
	Code        codes.Code
	Detail      string
	ID          int32
	OrderAmount float32
	Ctx         context.Context
}

// ExecuteLocalTransaction 执行创建订单涉及的本地事务
func (ol *OrderListener) ExecuteLocalTransaction(msg *primitive.Message) primitive.LocalTransactionState {

	zap.S().Infof("进入本地事务")
	// 从ctx中获取当前的父级span
	parentSpan := opentracing.SpanFromContext(ol.Ctx)

	orderInfo := model.OrderInfo{}
	err := json.Unmarshal(msg.Body, &orderInfo)
	// 从购物车中获取选中的商品
	var shopCarts []model.ShoppingCart
	GoodsToNum := make(map[int32]int32) // 维护GoodId对应商品的数量

	zap.S().Info(opentracing.GlobalTracer())
	// 获取查询购物车的追踪
	shopCartSpan := opentracing.GlobalTracer().StartSpan("select_shopcarts_local", opentracing.ChildOf(parentSpan.Context()))

	// 调用查询购物车的接口
	if result := global.DB.Where(&model.ShoppingCart{User: orderInfo.User, Checked: true}).Find(&shopCarts); result.RowsAffected == 0 {
		ol.Code = codes.InvalidArgument
		ol.Detail = "找不到对应结算的商品"
		return primitive.RollbackMessageState // 并没有进行扣减库存，所以不能够进行库存归还
	}
	// 结束当前的span
	shopCartSpan.Finish()

	var goodIds []int32 // 获取购物车订单列表
	for _, good := range shopCarts {
		// 记录购买的商品ID和对应的购买量 用于计算总价
		GoodsToNum[good.Goods] = good.Nums
		goodIds = append(goodIds, good.Goods)
	}

	// 商品查询的链路
	goodsQuerySpan := opentracing.GlobalTracer().StartSpan("goods_query_srv", opentracing.ChildOf(parentSpan.Context()))
	// 新建订单时需要查询商品价格，然后进行计算，涉及商品子服务
	goods, err := global.GoodsSrvClient.BatchGetGoods(context.Background(), &proto.BatchGoodsIdInfo{
		Id: goodIds,
	})
	if err != nil {
		ol.Code = codes.InvalidArgument
		ol.Detail = "找不到对应结算的商品"
		return primitive.RollbackMessageState
	}
	goodsQuerySpan.Finish()

	// 计算整个订单的总价格
	var orderAmount float32
	// 维护订单中的商品信息
	var orderGoods []*model.OrderGoods
	// 作为扣减订单请求的参数
	var goodsInfos []*proto.GoodsInvInfo

	for _, good := range goods.Data {
		// 订单总价 = 所有的 (商品价格 * 购买数量) 我们可以通过购物车获取购买数量
		orderAmount += good.ShopPrice * float32(GoodsToNum[good.Id])
		// 将当前商品的信息维护到订单商品信息切片 --- 后续维护到表中
		orderGoods = append(orderGoods, &model.OrderGoods{
			Goods:      good.Id,
			GoodsName:  good.Name,
			GoodsPrice: good.ShopPrice,
			Nums:       GoodsToNum[good.Id],
			GoodsImage: good.GoodsFrontImage,
			// OrderId还没有进行维护，因为我们这时并没有创建order表
		})
		// 维护一个商品ID-数目的切片
		goodsInfos = append(goodsInfos, &proto.GoodsInvInfo{
			GoodsId: good.Id,
			Num:     GoodsToNum[good.Id],
		})
	}

	inventorySellSpan := opentracing.GlobalTracer().StartSpan("inventory_sell_srv", opentracing.ChildOf(parentSpan.Context()))

	// 需要进行库存扣减，涉及库存子服务，并且需要最终一致性方案来保证分布式场景下的事务
	// 注意如果扣减库存的业务成功，但是其他模块失败，那么这时候需要进行commit归还库存
	_, err = global.InventorySrvClient.Sell(context.Background(), &proto.SellInfo{
		OrderSn:   orderInfo.OrderSn,
		GoodsInfo: goodsInfos,
	})
	if err != nil {
		ol.Code = codes.InvalidArgument
		ol.Detail = "扣减库存失败"
		return primitive.RollbackMessageState // 说明没扣库存，所以要阻止归还库存
	}
	inventorySellSpan.Finish()

	// 写入数据到订单信息表、订单商品信息表
	orderInfo.OrderMount = orderAmount

	// 本地事务处理订单表、购物车表的维护
	tx := global.DB.Begin()

	orderCreateSpan := opentracing.GlobalTracer().StartSpan("save_order_local", opentracing.ChildOf(parentSpan.Context()))
	// 保存订单表，并获得订单ID
	result := tx.Save(&orderInfo)
	if result.Error != nil {
		tx.Rollback()
		ol.Code = codes.Internal
		ol.Detail = "创建订单失败"
		return primitive.CommitMessageState
	}
	orderCreateSpan.Finish()
	// 创建订单成功后，需要将部分数据交由OrderListener
	ol.ID = orderInfo.ID
	ol.OrderAmount = orderInfo.OrderMount

	// 将订单ID维护到订单商品表
	for _, orderGood := range orderGoods {
		orderGood.Order = orderInfo.ID
	}

	orderGoodsSpan := opentracing.GlobalTracer().StartSpan("save_order_goods_local", opentracing.ChildOf(parentSpan.Context()))
	// 保存订单商品表
	result = tx.CreateInBatches(orderGoods, 100) // 每次至多插入100个，不满100个就按照实际数量
	if result.Error != nil {
		tx.Rollback()
		ol.Code = codes.Internal
		ol.Detail = "批量创建商品订单失败"
		return primitive.CommitMessageState
	}
	orderGoodsSpan.Finish()

	deleteShopCartsSpan := opentracing.GlobalTracer().StartSpan("delete_shopcarts_local", opentracing.ChildOf(parentSpan.Context()))
	// 从购物车中移除购买到商品
	result = tx.Where(&model.ShoppingCart{User: orderInfo.User, Checked: true}).Delete(&model.ShoppingCart{})
	if result.Error != nil {
		tx.Rollback()
		ol.Code = codes.Internal
		ol.Detail = "移除购物车商品失败"
		return primitive.CommitMessageState
	}
	deleteShopCartsSpan.Finish()

	// 提交事务之前，发送延迟消息来防止下订单，但是未支付的操作
	// 订单服务作为生产者进行延迟消息发送，然后订单服务同时作为消费者消费这条延迟消息，判断是否出现下单但是未支付

	//messageSendSpan := opentracing.GlobalTracer().StartSpan("send_queue_message_mq", opentracing.ChildOf(parentSpan.Context()))

	msgDelay := primitive.NewMessage("OrderTimeOut", msg.Body)
	msgDelay.WithDelayTimeLevel(16) // 设置30分钟超时
	_, err = global.ProducerOrderTimeout.SendSync(context.Background(), msgDelay)

	if err != nil {
		tx.Rollback()
		zap.S().Info("发送延迟消息失败，%s", err.Error())
		ol.Code = codes.Internal
		ol.Detail = "发送延迟消息失败"
		return primitive.CommitMessageState
	}
	//messageSendSpan.Finish()

	tx.Commit()
	ol.Code = codes.OK
	return primitive.RollbackMessageState // 扣除库存整个业务成功，进行rollback，阻止半消息去归还库存
}

// CheckLocalTransaction 进行消息回查
func (ol *OrderListener) CheckLocalTransaction(msg *primitive.MessageExt) primitive.LocalTransactionState {

	zap.S().Info("进入消息回查模块")

	orderInfo := model.OrderInfo{}
	_ = json.Unmarshal(msg.Body, &orderInfo)

	// 从订单表中进行查询OrderSn，看一下是否有这一条数据，有的话就进行回滚不进行库存归还，没有的话就进行库存归还
	result := global.DB.Where(model.OrderInfo{OrderSn: orderInfo.OrderSn}).First(&orderInfo)
	zap.S().Info(orderInfo)
	if result.RowsAffected == 0 { // 并不存在订单，但是这时候库存到底有没有扣减，可能是sell前就结束了，失败，也可能是sell失败了
		zap.S().Info("需要进行库存的归还")
		return primitive.CommitMessageState
	}
	return primitive.RollbackMessageState
}
